﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

using System.Diagnostics;
using SimpleIR;

namespace InterpreterCore
{
	/// <summary>
	/// This class represents a composite value (struct or internals of an object) as a sorted list of IRBasicAccessPath and SimpleMemoryValue entries.
	/// Thus if you have a struct made of structs they will all appear flattened with the BAP info providing any nesting.
	/// </summary>
	internal static class CompositeValue
	{
		internal static CompositeValueEntry[] MakeCompositeValue(IRStructType oftype)
		{
			Debug.Assert(oftype.TypeDescription.AllTerminalAccessPaths.Count != 0);

			List<IRBasicAccessPath> tapl = oftype.TypeDescription.AllTerminalAccessPaths;

			CompositeValueEntry[] vmap = new CompositeValueEntry[tapl.Count];
			for(int i = 0; i < tapl.Count; ++i)
				vmap[i] = new CompositeValueEntry(tapl[i]);

			return vmap;
		}

		internal static CompositeValueEntry[] MakeCompositeValue(IRClassType oftype)
		{
			Debug.Assert(oftype.TypeDescription.AllTerminalAccessPaths.Count != 0);

			List<IRBasicAccessPath> tapl = oftype.TypeDescription.AllTerminalAccessPaths;

			CompositeValueEntry[] vmap = new CompositeValueEntry[tapl.Count];
			for(int i = 0; i < tapl.Count; ++i)
				vmap[i] = new CompositeValueEntry(tapl[i]);

			return vmap;
		}

		internal static string Stringify(CompositeValueEntry[] vmap)
		{
			StringBuilder sb = new StringBuilder("{");

			for(int i = 0; i < vmap.Length; ++i)
				sb.Append(String.Format("{0}{1}->{2}", (i != 0 ? ", " : ""), vmap[i].Offset.FieldOnlyDisplayString(), vmap[i].Value.ToString()));

			sb.Append("}");

			return sb.ToString();
		}

		/// <summary>
		/// The value formatted as a string for display in the debugger.
		/// </summary>
		internal static string DebuggerString(CompositeValueEntry[] vmap)
		{
			StringBuilder sb = new StringBuilder("{");

			for(int i = 0; i < vmap.Length; ++i)
			{
				if(i != 0)
					sb.Append(", ");

				sb.Append(String.Format("{0}->{1}", vmap[i].Offset.FieldOnlyDisplayString(), vmap[i].Value.DebuggerString()));
			}

			sb.Append("}");

			return sb.ToString();
		}

		/// <summary>
		/// The value formatted as a string in a format for emitting in the trace.
		/// </summary>
		internal static string EmitForTrace(CompositeValueEntry[] vmap)
		{
			StringBuilder sb = new StringBuilder("{");

			for(int i = 0; i < vmap.Length; ++i)
			{
				if(i != 0)
					sb.Append(",");

				sb.Append(String.Format("{0}${1}->{2}", vmap[i].Offset.PathID, vmap[i].Offset.FieldOnlyDisplayString(), vmap[i].Value.EmitForTrace()));
			}

			sb.Append("}");

			return sb.ToString();
		}

		/// <summary>
		/// The value formatted and output for a heap dump.
		/// </summary>
		internal static void SerializeForHeapDump(System.IO.TextWriter dumpstr, CompositeValueEntry[] vmap)
		{
			dumpstr.Write('{');

			for(int i = 0; i < vmap.Length; ++i)
			{
				if(i != 0)
					dumpstr.Write(',');

				dumpstr.Write(vmap[i].Offset.PathID);
				dumpstr.Write("->");
				vmap[i].Value.SerializeForHeapDump(dumpstr);
			}

			dumpstr.Write('}');
		}

		/// <summary>
		/// Parse value of the given type from a string.
		/// </summary>
		internal static CompositeValueEntry[] ParseValue(IRType tt, string val)
		{
			Debug.Assert(tt is IRClassType || tt is IRStructType);

			string cv = val.Substring(1, val.Length - 2); //remove {}
			string[] entries = cv.Split(new char[] { ',' });

			List<IRBasicAccessPath> tapl = tt.TypeDescription.AllTerminalAccessPaths;
			CompositeValueEntry[] vmap = new CompositeValueEntry[tapl.Count];

			Debug.Assert(entries.Length == tapl.Count);

			for(int i = 0; i < tapl.Count; ++i)
			{
				string ev = entries[i];

				int dpos = ev.IndexOf('$');
				int arrowpos = ev.IndexOf("->");

				int oidepos = (dpos != -1) ? dpos : arrowpos;
				int svalpos = arrowpos + 2;

				string offsetidstr = ev.Substring(0, oidepos);
				int offsetid = Int32.Parse(offsetidstr);
				IRBasicAccessPath offset = GlobalInterpreterState.TProg.GetAccessPathWithID(offsetid);

				string valstr = ev.Substring(svalpos);
				SimpleMemoryValue smv = SimpleMemoryValue.ParseValue(offset.PathLast.FieldType, valstr);

				vmap[i] = new CompositeValueEntry(offset, smv);
			}

			return vmap;
		}

		///////////////////////////////////////////////////////////////////////////////
		//How to read from and assign to the composite value.

		internal static void InitializeToZeroValues(CompositeValueEntry[] cvem)
		{
			for(int i = 0; i < cvem.Length; ++i)
			{
				IRType tt = cvem[i].Offset.PathLast.FieldType;
				if(tt is IRPrimitiveIntegerType || tt is IREnumType)
				{
					if(tt is IREnumType)
						tt = ((IREnumType)tt).UnerlyingNumericType;

					NumericValue nv = NumericValue.MakeZeroValueOfType((IRPrimitiveNumericType)tt);
					cvem[i] = new CompositeValueEntry(cvem[i].Offset, SimpleMemoryValue.ConvertNumericValToSimpleMemoryVersion(nv));
				}
				else
				{
					PtrValue pv = PtrValue.MakeNullPtr((IRPtrType)tt);
					cvem[i] = new CompositeValueEntry(cvem[i].Offset, SimpleMemoryValue.ConvertPtrValToSimpleMemoryVersion(pv));
				}
			}
		}

		internal static PtrValue ReadPassablePtrValueFrom(IRBasicAccessPath bap, CompositeValueEntry[] vmap)
		{
			Debug.Assert(!bap.IsEmptyAccessPath);
			Debug.Assert(bap.PathLast.FieldType is IRPtrType);

			CompositeValueEntry cenv = new CompositeValueEntry(bap);
			int pos = Array.BinarySearch(vmap, cenv);

			return vmap[pos].Value.ConvertToPassablePtrValue();
		}

		internal static void StorePassablePtrValTo(PtrValue pv, IRBasicAccessPath bap, CompositeValueEntry[] vmap)
		{
			Debug.Assert(!bap.IsEmptyAccessPath);
			Debug.Assert(bap.PathLast.FieldType is IRPtrType);
			Debug.Assert(pv.ValType == bap.PathLast.FieldType);

			CompositeValueEntry cenv = new CompositeValueEntry(bap);
			int pos = Array.BinarySearch(vmap, cenv);

			vmap[pos] = new CompositeValueEntry(bap, SimpleMemoryValue.ConvertPtrValToSimpleMemoryVersion(pv));
		}

		internal static NumericValue ReadPassableNumericFrom(IRBasicAccessPath bap, CompositeValueEntry[] vmap)
		{
			Debug.Assert(!bap.IsEmptyAccessPath);
			Debug.Assert(bap.PathLast.FieldType is IRPrimitiveNumericType || bap.PathLast.FieldType is IREnumType);

			CompositeValueEntry cenv = new CompositeValueEntry(bap);
			int pos = Array.BinarySearch(vmap, cenv);

			return vmap[pos].Value.ConvertToPassableNumericValue();
		}

		internal static void StorePassableNumericValTo(NumericValue nv, IRBasicAccessPath bap, CompositeValueEntry[] vmap)
		{
			Debug.Assert(!bap.IsEmptyAccessPath);
			Debug.Assert(bap.PathLast.FieldType is IRPrimitiveNumericType || bap.PathLast.FieldType is IREnumType);
			Debug.Assert((bap.PathLast.FieldType is IRPrimitiveNumericType && nv.ValType == bap.PathLast.FieldType) || (bap.PathLast.FieldType is IREnumType && nv.ValType == ((IREnumType)bap.PathLast.FieldType).UnerlyingNumericType));

			CompositeValueEntry cenv = new CompositeValueEntry(bap);
			int pos = Array.BinarySearch(vmap, cenv);

			vmap[pos] = new CompositeValueEntry(bap, SimpleMemoryValue.ConvertNumericValToSimpleMemoryVersion(nv));
		}

		internal static CompositeValueEntry[] ReadCompositeFrom(IRBasicAccessPath bap, CompositeValueEntry[] vmap, IRType rootloctype)
		{
			if(bap.IsEmptyAccessPath)
			{
				Debug.Assert(rootloctype is IRStructType);

				CompositeValueEntry[] cvee = (CompositeValueEntry[])vmap.Clone();

				return cvee;
			}
			else
			{
				Debug.Assert(bap.PathLast.FieldType is IRStructType);

				int fpos = Array.FindIndex(vmap, (CompositeValueEntry cve) => bap.IsAccessPathPrefixOf(cve.Offset));
				int epos = Array.FindLastIndex(vmap, (CompositeValueEntry cve) => bap.IsAccessPathPrefixOf(cve.Offset));

				CompositeValueEntry[] cvee = new CompositeValueEntry[(epos - fpos) + 1];
				for(int i = fpos; i <= epos; ++i)
				{
					IRBasicAccessPath tailp = GlobalInterpreterState.TProg.GetAccessPathTailK(vmap[i].Offset, bap.Path.Length);
					cvee[i - fpos] = new CompositeValueEntry(tailp, vmap[i].Value);
				}

				return cvee;
			}
		}

		internal static void StoreCompositeTo(CompositeValueEntry[] nv, IRBasicAccessPath bap, CompositeValueEntry[] vmap, IRType rootloctype)
		{
			Debug.Assert(nv.Length != 0);

			if(bap.IsEmptyAccessPath)
			{
				Debug.Assert(rootloctype is IRStructType);

				Debug.Assert(nv.Length == vmap.Length);

				Array.Copy(nv, vmap, nv.Length);
			}
			else
			{
				Debug.Assert(bap.PathLast.FieldType is IRStructType);

				IRBasicAccessPath sbap = GlobalInterpreterState.TProg.GetAccessPathExtensionPost_WPath(bap, nv.First().Offset);
				int fpos = Array.FindIndex(vmap, (CompositeValueEntry cve) => cve.Offset == sbap);

				IRBasicAccessPath ebap = GlobalInterpreterState.TProg.GetAccessPathExtensionPost_WPath(bap, nv.Last().Offset);
				int epos = Array.FindLastIndex(vmap, (CompositeValueEntry cve) => cve.Offset == ebap);

				for(int i = fpos; i <= epos; ++i)
					vmap[i] = new CompositeValueEntry(vmap[i].Offset, nv[i - fpos].Value);
			}
		}
	}

	internal struct CompositeValueEntry : IEquatable<CompositeValueEntry>, IComparable<CompositeValueEntry>
	{
		/// <summary>
		/// The offset that the value is stored at, we ONLY use this for sorting and equality.
		/// </summary>
		public readonly IRBasicAccessPath Offset;

		/// <summary>
		/// The value that is stored at the offset.
		/// </summary>
		public readonly SimpleMemoryValue Value;

		public CompositeValueEntry(IRBasicAccessPath offset)
		{
			Debug.Assert(!offset.IsEmptyAccessPath);

			this.Offset = offset;
			this.Value = SimpleMemoryValue.MakeInitialSimpleMemoryValueOfType(offset.PathLast.FieldType);
		}

		public CompositeValueEntry(IRBasicAccessPath offset, SimpleMemoryValue value)
		{
			Debug.Assert(!offset.IsEmptyAccessPath);

			this.Offset = offset;
			this.Value = value;
		}

		public override string ToString()
		{ return String.Format("{0} {1}", this.Offset.FieldOnlyDisplayString(), this.Value.ToString()); }

		public static bool operator ==(CompositeValueEntry a, CompositeValueEntry b)
		{ return a.Equals(b); }

		public static bool operator !=(CompositeValueEntry a, CompositeValueEntry b)
		{ return !a.Equals(b); }

		public static bool operator <(CompositeValueEntry a, CompositeValueEntry b)
		{ return a.CompareTo(b) < 0; }

		public static bool operator >(CompositeValueEntry a, CompositeValueEntry b)
		{ return a.CompareTo(b) > 0; }

		public override int GetHashCode()
		{ return this.Offset.GetHashCode(); }

		public override bool Equals(object obj)
		{ return (obj is CompositeValueEntry) && this.Equals((CompositeValueEntry)obj); }

		public bool Equals(CompositeValueEntry other)
		{ return this.Offset == other.Offset; }

		public int CompareTo(CompositeValueEntry other)
		{ return this.Offset.CompareTo(other.Offset); }
	}
}
